package com.jycode.commons.genericdao;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.persistence.NamedQuery;

import org.hibernate.Criteria;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.hibernate.type.Type;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.SessionFactoryUtils;

import com.jycode.commons.genericdao.finder.FinderArgumentTypeFactory;
import com.jycode.commons.genericdao.finder.impl.SimpleFinderArgumentTypeFactory;

/**
 * DAO的书写略为复杂一点, 需要为每个DAO接口写一句到findByNamedQuery/iterateByNamedQuery的调用语句。
 * 但是好处在于每个DAO只要启用了@Repository注解，就无需去Spring配置文件中手工编写一次相应的daoBean的声明了。
 *
 * @author "banlyst <banlyst@gmail.com>"
 *
 * @param <T>
 * @param <ID>
 */
public class GenericHibernateDAO <T, ID extends Serializable> implements GenericDAO2<T, ID> {
	private Class<T> persistentClass;

	@Autowired
    private SessionFactory sessionFactory;

    final static FinderArgumentTypeFactory simpleFinderArgumentTypeFactory = new SimpleFinderArgumentTypeFactory();

    public GenericHibernateDAO(Class<T> persistentClass) {
        this.persistentClass = persistentClass;
    }


    protected Session getSession() {
        final boolean allowCreate = true;
        return SessionFactoryUtils.getSession(sessionFactory, allowCreate);
    }

    public Class<T> getPersistentClass() {
        return persistentClass;
    }

    protected String getNamingWithStrategy(String queryNameSimple)
    {
    	if(queryNameSimple.startsWith("do")){
    		queryNameSimple = queryNameSimple.substring(2, 3).toLowerCase() + queryNameSimple.substring(3);
    	}else if(queryNameSimple.startsWith("iterate")){
    		queryNameSimple = "list" + queryNameSimple.substring(7);
    	}else if(queryNameSimple.startsWith("get")){
    		queryNameSimple = "find" + queryNameSimple.substring(3);
    	}else if(queryNameSimple.startsWith("read")){
    		queryNameSimple = "find" + queryNameSimple.substring(4);
    	}
    	return persistentClass.getSimpleName() + "." + queryNameSimple;
    }

	private FinderArgumentTypeFactory getArgumentTypeFactory() {
		return simpleFinderArgumentTypeFactory;
	}

	private Query FinderNamedQueryFromAnnotation(String queryName, Annotation nqs)
	{
    	NamedQuery[] nqColl;
		try {
			nqColl = (NamedQuery[])nqs.annotationType().getMethod("value").invoke(nqs);
		} catch (Exception e) {
			return null;
		}
    	for (NamedQuery nq : nqColl) {
    		if(nq.name().equals(queryName)){
    			return getSession().createQuery(nq.query());
    		}
    	}
		return null;
	}

	protected Query prepareQuery(String queryNameSimple, Object[] queryArgs)
    {
//    	Query q = getSession().getNamedQuery(queryName);
//    	return q;
        final String queryName = getNamingWithStrategy(queryNameSimple);
        Query namedQuery = null;
        try{
        	//首先尝试从基类BaseEntity的注解中查找全限定名的命名查询
        	Annotation nqs = persistentClass.getAnnotation(javax.persistence.NamedQueries.class);
        	namedQuery = FinderNamedQueryFromAnnotation(queryName, nqs);

        	if(namedQuery==null && persistentClass.getSuperclass()!=null){
        		nqs = persistentClass.getSuperclass().getAnnotation(javax.persistence.NamedQueries.class);
        		namedQuery = FinderNamedQueryFromAnnotation(queryName, nqs);
        	}

        	//如果基类中没有定义该命名查询注解，则进入标准搜索算法中
        	if(namedQuery==null)
        		namedQuery = getSession().getNamedQuery(queryName);
        }catch(Exception ex){
        	// 如果不能找到注解中的命名查询,则尝试执行存储过程.
			StringBuffer sbStoreProcSql = new StringBuffer();

			//查找存储过程，形如“USER_findByName”
			sbStoreProcSql.append(queryName.replaceAll("\\.", "_"));

			//执行存储过程命令
			{
				StringBuffer sb1 = new StringBuffer();
				sb1.append("{ call ").append(
						sbStoreProcSql.toString()).append("(");
				for (int i = 0; i < queryArgs.length; i++) {
					sb1.append("?");
					if (queryArgs.length - 1 != i)
						sb1.append(",");
				}
				sb1.append(") }");
				try {
					namedQuery = getSession().createSQLQuery(sb1.toString());

					if(false){
						//返回带有列名的Hashmap值对照表
						namedQuery.setResultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP);
					}else if(false){
						//仅仅返回给出的下列字段及转换到指定的数据类型，忽略结果基中的其他字段
						SQLQuery q = ((SQLQuery)namedQuery);
						q.addScalar("ID", org.hibernate.Hibernate.LONG)
						.addScalar("FIRST_NAME")
						.addScalar("PASSWORD");
					}else{
						//返回Hibernate实体类类型
						((SQLQuery)namedQuery).addEntity(persistentClass);
					}
				} catch (Exception exx) {
				}
				// Note
				// 存储过程的直接调用语法如下
				// Connection con = null;
				// CallableStatement proc=null;
				// con = this.getSession().connection();
				// proc = con.prepareCall("{ call sp_new_game(?,?,?,?) }");
				// //此语法适用于MySQL/Oracle/DB2
				// proc.setString(1,login);
				// proc.setInt(2,tableId);
				// proc.setInt(3, 4);
				// proc.registerOutParameter(4, java.sql.Types.INTEGER);
				// // proc.registerOutParameter(5, java.sql.Types.VARCHAR);
				// proc.execute();
				// result=proc.getInt(4);
			}
        	//HibernateDaoSupport hds = new HibernateDaoSupport();
        	//hds.getHibernateTemplate().findByNamedQuery(queryName);
        }
        String[] namedParameters = namedQuery.getNamedParameters();
        if(namedParameters.length==0)
        {
            setPositionalParams(queryArgs, namedQuery);
        } else {
            setNamedParams(namedParameters, queryArgs, namedQuery);
        }
        return namedQuery;
    }

    private void setPositionalParams(Object[] queryArgs, Query namedQuery)
    {
        // Set parameter. Use custom Hibernate Type if necessary
        if(queryArgs!=null)
        {
            for(int i = 0; i < queryArgs.length; i++)
            {
                Object arg = queryArgs[i];
                Type argType = getArgumentTypeFactory().getArgumentType(arg);
                if(argType != null)
                {
                    namedQuery.setParameter(i, arg, argType);
                }
                else
                {
                    namedQuery.setParameter(i, arg);
                }
            }
        }
    }


	@SuppressWarnings("unchecked")
	private void setNamedParams(String[] namedParameters, Object[] queryArgs, Query namedQuery)
    {
        // Set parameter. Use custom Hibernate Type if necessary
        if(queryArgs!=null)
        {
            for(int i = 0; i < queryArgs.length; i++)
            {
                Object arg = queryArgs[i];
                Type argType = getArgumentTypeFactory().getArgumentType(arg);
                if(argType != null)
                {
                    namedQuery.setParameter(namedParameters[i], arg, argType);
                }
                else
                {
                    if(arg instanceof Collection) {
                        namedQuery.setParameterList(namedParameters[i], (Collection) arg);
                    }
                    else
                    {
                        namedQuery.setParameter(namedParameters[i], arg);
                    }
                }
            }
        }
    }

	public T uniqueResult(List<T> list)
	{
		if(list == null) return null;
		if(list.size()>0) return list.get(0);
		return null;
	}

    /**
     * Use this inside subclasses as a convenience method.
     */
    @SuppressWarnings("unchecked")
    protected List<T> findByCriteria(Criterion... criterion) {
        Criteria crit = getSession().createCriteria(getPersistentClass());
        for (Criterion c : criterion) {
            crit.add(c);
        }
        return crit.list();
   }




	@SuppressWarnings("unchecked")
	public ID create(T newInstance) {
		return (ID) getSession().save(newInstance);
	}

	public void delete(T persistentObject) {
		getSession().delete(persistentObject);
	}

	@SuppressWarnings("unchecked")
	public T read(ID id) {
		return (T) getSession().get(getPersistentClass(), id);//return (T) findById(id, false);
	}

	public T loadWithLock(ID id) {
		return (T) findById(id, true);
	}

	public T loadWithoutLock(ID id) {
		return (T) findById(id, false);
	}

	public void update(T transientObject) {
		getSession().update(transientObject);
	}


    @SuppressWarnings("unchecked")
	public T findById(ID id, boolean lock) {
        T entity;
        if (lock)
            entity = (T) getSession().load(getPersistentClass(), id, LockMode.UPGRADE);
        else
            entity = (T) getSession().load(getPersistentClass(), id);

        return entity;
    }

    @SuppressWarnings("unchecked")
    public List<T> findAll() {
        return findByCriteria();
    }

    @SuppressWarnings("unchecked")
    public List<T> findByExample(T exampleInstance) {
        return findByCriteria( Example.create(exampleInstance) );
    }

    @SuppressWarnings("unchecked")
	public List<T> findByNamedQuery(String queryName, Object... queryArgs)
    {
    	Query q = prepareQuery(queryName, queryArgs);
    	return (List<T>) q.list();
    }

    @SuppressWarnings("unchecked")
	public List<T> findByNamedQuery(String queryName, Map<String, Object> queryArgs)
    {
    	Query q = prepareQuery(queryName, queryArgs.values().toArray());
//    	for(Entry<String, Object> val : queryArgs.entrySet())
//    	{
//    		q.setParameter(val.getKey(), val.getValue());
//    	}
    	return (List<T>) q.list();
    }

    @SuppressWarnings("unchecked")
	public List<T> findByNamedQuery(String queryName, final int from, final int max, Object... queryArgs)
    {
    	Query q = prepareQuery(queryName, queryArgs);
    	if (from >= 0 && max > 0) {
            q.setFirstResult(from).setMaxResults(max);
        }
    	return (List<T>) q.list();
    }

    @SuppressWarnings("unchecked")
	public List<T> findByNamedQuery(String queryName, final int from, final int max, Map<String, Object> queryArgs)
    {
    	Query q = prepareQuery(queryName, queryArgs.values().toArray());
    	if (from >= 0 && max > 0) {
            q.setFirstResult(from).setMaxResults(max);
        }
    	return (List<T>) q.list();
    }



    @SuppressWarnings("unchecked")
    public T makePersistent(T entity) {
        getSession().saveOrUpdate(entity);
        return entity;
    }

    public void makeTransient(T entity) {
        getSession().delete(entity);
    }




	@SuppressWarnings("unchecked")
	public Object executeNativeQuery(String queryStringSql) {
		List list = createNativeQuery(queryStringSql).list();
		if(list.size()>0)
			return list.get(0);
		return null;
	}

	@SuppressWarnings("unchecked")
	public T executeNativeQueryToEntity(String queryStringSql) {
		List list = createNativeQuery(queryStringSql).addEntity(this.getPersistentClass()).list();
		if(list.size()>0)
			return (T)list.get(0);
		return null;
	}

	public SQLQuery createNativeQuery(String queryStringSql)
	{
		return getSession().createSQLQuery(queryStringSql);
	}

	public int executeNativeUpdate(String queryStringSql) {
		return createNativeQuery(queryStringSql).executeUpdate();
	}


	@SuppressWarnings("unchecked")
	public Object executeQuery(String queryString) {
		List list = getSession().createQuery(queryString).list();
		if(list.size()>0)
			return list.get(0);
		return null;
	}


	public int executeUpdate(String queryString) {
		return getSession().createQuery(queryString).executeUpdate();
	}


	@SuppressWarnings("unchecked")
	public Iterator<T> iterateByNamedQuery(String queryName, Object... queryArgs) {
    	Query q = prepareQuery(queryName, queryArgs);
        if(q instanceof SQLQuery){
        	return (Iterator<T>) q.list().iterator();
        }
        return (Iterator<T>) q.iterate();
	}


	@SuppressWarnings("unchecked")
	public Iterator<T> iterateByNamedQuery(String queryName, Map<String, Object> queryArgs) {
    	Query q = prepareQuery(queryName, queryArgs.values().toArray());
    	return (Iterator<T>) q.iterate();
	}


	@SuppressWarnings("unchecked")
	public Iterator<T> iterateByNamedQuery(String queryName, int from, int max, Object... queryArgs) {
    	Query q = prepareQuery(queryName, queryArgs);
    	if (from >= 0 && max > 0) {
            q.setFirstResult(from).setMaxResults(max);
        }
    	return (Iterator<T>) q.iterate();
	}


	@SuppressWarnings("unchecked")
	public Iterator<T> iterateByNamedQuery(String queryName, int from, int max, Map<String, Object> queryArgs) {
    	Query q = prepareQuery(queryName, queryArgs.values().toArray());
    	if (from >= 0 && max > 0) {
            q.setFirstResult(from).setMaxResults(max);
        }
    	return (Iterator<T>) q.iterate();
	}
}
